package com.dycong.common.database.mongoDB;

import com.ifunpay.util.common.StringUtil;
import com.ifunpay.util.enums.FieldTypeEnum;
import com.ifunpay.util.jsf.FacesUtil;
import com.ifunpay.util.mongo.MongoConfig;
import com.ifunpay.util.mongo.MongoUtil;
import com.mongodb.DBRef;
import com.mongodb.client.MongoCollection;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
import org.apache.commons.collections.IteratorUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by yu on 15-7-13.
 * todo 真正操作mongodb数据的基类，每一个Model可以继承以使用，也可以通过覆盖方法，增强/改变load等方法的行为，
 * todo 因为继承自org.primefaces.model.DataModel<T>(primefaces提供的懒加载抽象类)在页面需要数据的时候自动调用load()方法，
 * todo 从mongodb取出的数据都是以Document，或者List<Map>形式
 * todo 此处的Document是mongo java 驱动包提供的Document，Document implements Map<String, Object>, Serializable, Bson，
 * todo Document 提供了与json的交互静态方法，例如将json->Document的 parse(final String json)
 */
@Log4j
public class MongoLazyDataModel extends LazyDataModel<Document> {

    @Getter
    @Setter
    Date startDate;

    @Getter
    @Setter
    Date endDate;

    String dateKey="date";

    @Setter
    @Getter
    protected String defaultSortField;

    @Getter
    @Setter
    protected SortOrder defaultSortOrder=SortOrder.DESCENDING;

    final Document defaultFilter;

    final String collectionName;
    MongoCollection<Document> mongoCollection;


    public MongoLazyDataModel(String collectionName) {
        this(collectionName, new Document());
    }


    public MongoLazyDataModel(String collectionName, Document defaultFilter) {
        this.collectionName = collectionName;
        this.defaultFilter = defaultFilter;
        mongoCollection = MongoConfig.getCollection(collectionName);
    }

    public MongoLazyDataModel(String collectionName, Document defaultFilter, String defaultSortField) {
        this.collectionName = collectionName;
        this.defaultFilter = defaultFilter;
        mongoCollection = MongoConfig.getCollection(collectionName);
        this.defaultSortField = defaultSortField;
    }



    Document filter;

    Map<String, FieldTypeEnum> fieldTypeMap = new HashMap<>();


    int rowCount;

    @Override
    public int getRowCount() {
        return rowCount;
    }

    @Override
    public List<Document> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
            log.info("filters = "+filters);
        filter = new Document(defaultFilter);
        Document docu = new Document();
        if(startDate!=null) {
            docu.append("$gte",startDate);
        }
        if (endDate!=null){
            docu.append("$lte", endDate);
        }
        if (docu.size()>0)
            filter.append(dateKey,docu);


        filters.forEach((k, v) -> addFilter(filter, k, v));
        Document sort = new Document();
        if (sortField != null) {
            sort.append(sortField, sortOrder == SortOrder.ASCENDING ? 1 : -1);
        }else if (StringUtil.isNotEmpty(getDefaultSortField())){
            sort.append(defaultSortField, defaultSortOrder == SortOrder.ASCENDING ? 1 : -1);
        }
        List<Document> list = IteratorUtils.toList(mongoCollection.find(filter).skip(first).limit(pageSize).sort(sort).iterator());
        if(log.isDebugEnabled()){
            log.debug(list);
        }
        rowCount = (int) mongoCollection.count(filter);
        return list.stream().map(DocumentWrapper::new).collect(Collectors.toList());
    }

    @Override
    public Object getRowKey(Document object) {
        return object.get("_id");
    }

    @Override
    public Document getRowData(String rowKey) {
        return MongoUtil.get(collectionName, new ObjectId(rowKey))
                .orElse(MongoUtil.get(collectionName, rowKey)
                        .orElse(null)
                );
    }

    public static class DocumentWrapper extends Document {
        public DocumentWrapper(Document document) {
            super(document);
        }

        public Document fetchRef(String key) {
            Object value = super.get(key);
            if (value instanceof DBRef) {
                DBRef ref = (DBRef) value;
                return fetch(ref);
            } else {
                return null;
            }
        }

        public List<Document> fetchRefList(String key) {
            Object value = super.get(key);
            if (value instanceof List) {
                List<DBRef> list = (List<DBRef>) value;
                return list.stream()
                        .map(this::fetch)
                        .collect(Collectors.toList());
            } else {
                return new ArrayList<>();
            }
        }

        public Document fetch(DBRef ref) {
            String collection = ref.getCollectionName();
            Object id = ref.getId();
            Document doc = MongoUtil.get(collection, id)
                    .orElse(MongoUtil
                                    .get(collection, new ObjectId(id.toString()))
                                    .orElse(new Document())
                    );
            return new DocumentWrapper(doc);
        }
    }



    public MongoLazyDataModel addFieldType(String fieldName, FieldTypeEnum type) {
        fieldTypeMap.put(fieldName, type);
        return this;
    }

    public MongoLazyDataModel addFieldTypes(Map<String, FieldTypeEnum> map) {
        fieldTypeMap.putAll(map);
        return this;
    }


    private Document addFilter(Document originFilter, String filterName, Object value) {
        FieldTypeEnum type = fieldTypeMap.get(filterName);
        if (type == null) {
            type = FieldTypeEnum.DEFAULT;
        }
        switch (type) {
            case NUMBER:
                originFilter.append(filterName, Integer.valueOf(value+""));
                break;
            case BOOLEAN:
                originFilter.append(filterName, Boolean.valueOf(value.toString()));
                break;
            default:
                originFilter.append(filterName, new Document("$regex", ".*" + value + ".*"));
                break;
        }
        return originFilter;
    }
    Map<String, MongoLazyDataModel> map = new HashMap<>();
    public MongoLazyDataModel getModel(String collectionName) {
        MongoLazyDataModel mongoLazyDataModel = map.get(collectionName);
        if (mongoLazyDataModel == null) {
            synchronized ((getClass().getName() + collectionName.intern())) {
                mongoLazyDataModel = map.get(collectionName);
                if (mongoLazyDataModel == null) {
                    mongoLazyDataModel = new MongoLazyDataModel(collectionName);
                    map.put(collectionName, mongoLazyDataModel);
                }
            }
        }
        return mongoLazyDataModel;
    }

    public List<Document> collectionDataList(String collectionName) {
        return MongoUtil.find(collectionName, 0, 1000);
    }

    public static void delete(String collectionName, String id) {
        try {
            MongoUtil.deleteOne(collectionName, new Document("_id", new ObjectId(id)));
            FacesUtil.info("删除成功");
        } catch (Exception e) {
            FacesUtil.error("删除失败");
            log.info("error delete collectionName:" + collectionName + " id:" + id + "\r\n", e);
        }

    }
}
